package compiler;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import mips.instructions.Instruction;

public class StaticRecompiler {

	private static final Set<Instruction> jType;
	
	static {
		jType = new HashSet<Instruction>();
		jType.add(mips.instructions.BEQ.getInstance());
		jType.add(mips.instructions.BNE.getInstance());
		jType.add(mips.instructions.J.getInstance());
		jType.add(mips.instructions.JAL.getInstance());
		jType.add(mips.instructions.JALR.getInstance());
		jType.add(mips.instructions.JR.getInstance());		
	}
	
	public static void emitBasicBlocks(Collection<Integer> targets) throws IOException {
		
		FileOutputStream fileOS;	
		PrintStream writerStream;
		
		fileOS = new FileOutputStream("src/main/java/recompiled/InitCpuTargets.java");
		writerStream = new PrintStream(fileOS);
		
		String output = "package recompiled;\n\n";
		output += "public class InitCpuTargets {\n";
		output += "\tpublic static void initCpuTargets() {\n";
		for(Integer target : targets) {
			
			output += "\t\tmips.CPU.targets.put("+String.format("0x%08X", target)+", "+String.format("%08X", target)+".getBasicBlock());\n";
		}
		output += "\t}\n";
		output += "}\n";

		writerStream.print(output);
		
		writerStream.close();
		fileOS.close();
		
		for(Integer target : targets) {

			//emitFile = new File("/home/jnmartin/Desktop/workspace2/decaf64/src/main/java/recompiled/"+String.format("%08X", target)+".java");
			//emitFile.createNewFile();
			
			//System.setOut(new PrintStream(emitFile)); 
			
			fileOS = new FileOutputStream("src/main/java/recompiled/"+String.format("%08X", target)+".java");
			writerStream = new PrintStream(fileOS);
			
			int length = 0;
			
			int startPC = target.intValue();
			int instruction = n64.MemoryManager.readWord(startPC);
			int opcode = (instruction >> 26) & 0x0000003F;
			int funct = (instruction) & 0x0000003F;
			Instruction nextInst = Instruction.getOpcode(opcode);
			
			writerStream.println("package recompiled;");
			writerStream.println("");
			writerStream.println(String.format("/**\n * TARGET PC: 0x%08X\n */", target));
			writerStream.println("public class " + String.format("%08X", target) + " extends compiler.BasicBlock {");
			writerStream.println("");
			writerStream.println("\tprivate static final "+String.format("%08X", target)+" INSTANCE = new "+String.format("%08X", target)+"();");
			writerStream.println("");			
			writerStream.println("\tprivate "+String.format("%08X", target)+"(){}");
			writerStream.println("");			
			writerStream.println("\t/**\n\t * @see compiler.BasicBlock#getBasicBlock \n\t */");
			writerStream.println("\tpublic static compiler.BasicBlock getBasicBlock() {");
			writerStream.println("\t\treturn INSTANCE;");
			writerStream.println("\t}");
			writerStream.println("");
			
			writerStream.println("\t/**\n\t * {@inheritDoc}\n\t */");
			writerStream.println("\t@Override");
			writerStream.println("\tpublic void execute() throws mips.exceptions.MipsException {");
			writerStream.println("");			

			while(!jType.contains(nextInst)) {
				
				/*if(startPC >= 0xa00000e4) {
					
					System.out.println();
				}*/

				
				if(nextInst instanceof mips.instructions.SPECIAL) {
					
					Instruction retInst = Instruction.getSpecial(funct);
					
					if(jType.contains(retInst)) {
						
						break;
					}
				}
				
				writerStream.printf("\t\t//0x%08x \n", startPC);
				writerStream.printf("\t\t//0x%08x \n", instruction);
				
				if (Instruction.getOpcode(opcode) instanceof mips.instructions.SPECIAL) {
						
					writerStream.println("\t\t//" + Instruction.getOpcode(opcode).getName(instruction));
				} 
				else {
						
					writerStream.println("\t\t//" + Instruction.getOpcode(opcode).getName());
				}
				
				writerStream.println(Instruction.getOpcode(opcode).emit(instruction));
				writerStream.println("");					
				
				length++;
				
				startPC += 4;
				instruction = n64.MemoryManager.readWord(startPC);
				opcode = (instruction >> 26) & 0x0000003F;
				funct = (instruction) & 0x0000003F;				
				nextInst = Instruction.getOpcode(opcode);				
			}

			/**
			 * We have finished iterating over all non-jump and non-delay-slot instructions
			 * in this basic block.
			 * 
			 * Do final clean-up/house-keeping before function end.
			 */
			String branchEmit = "";
			//final int branchInstrPC = startPC;
			instruction = n64.MemoryManager.readWord(startPC);
			opcode = (instruction >> 26) & 0x0000003F;
			nextInst = Instruction.getOpcode(opcode); 
			length++;
		
			branchEmit += String.format("\t\t//0x%08x \n", startPC);
			branchEmit += String.format("\t\t//0x%08x \n", instruction);
			if (nextInst instanceof mips.instructions.SPECIAL) {
					
				branchEmit += "\t\t//" + nextInst.getName(instruction) + "\n";
			} 
			else {
					
				branchEmit += "\t\t//" + nextInst.getName() + "\n";
			}
			
			branchEmit += nextInst.emit(instruction) + "\n";			
			branchEmit += "\n";			

			String delayEmit = "";
			
			// finished all non-jump instructions, now emit the delay slot :o
			length++;
			startPC += 4;
			instruction = n64.MemoryManager.readWord(startPC);
			opcode = (instruction >> 26) & 0x0000003F;
			nextInst = Instruction.getOpcode(opcode);			
			

			delayEmit += String.format("\t\t//0x%08x \n", startPC);
			delayEmit += String.format("\t\t//0x%08x \n", instruction);
			if (Instruction.getOpcode(opcode) instanceof mips.instructions.SPECIAL) {
					
				delayEmit += "\t\t//" + Instruction.getOpcode(opcode).getName(instruction) + "\n";
			} 
			else {
					
				delayEmit += "\t\t//" + Instruction.getOpcode(opcode).getName() + "\n";
			}
			delayEmit += Instruction.getOpcode(opcode).emit(instruction) + "\n";			
			delayEmit += "\n";			

			writerStream.print(branchEmit);
			writerStream.print(delayEmit);
			
			
			// calculate branch/jump target
			// if target was reached during execution, emit a method call of the form
			//
			//		A00000DC.execute();
			//
			// after emitting the delay slot code
/**			if(targets.contains(branchInstrPC)) {
			
				// we have compiled code for this target
				
				// %08x
				writerStream.println("\t\t" + String.format("%08X", startPC).toUpperCase() + ".execute();");
			}
			
			else {
				
				// we didn't compile this branch target... doh!
				
				// go back into interpreter -- return; ? 
			}
			writerStream.println(""); */			
			
			// otherwise, emit a call into the interpreter
			// this leads to the interpreter requiring the ability to switch between
			// execution modes (interpret, static recompiled method calls, dynarec, etc).			
			
			// put that above code here :o

			writerStream.println("\t}");
			writerStream.println("");			

			writerStream.println("\t/**\n\t * {@inheritDoc}\n\t */");
			writerStream.println("\t@Override");			
			writerStream.println("\tpublic int blockLength() {");
//			writerStream.println("");			
			writerStream.println("\t\treturn " + (length+1) + ";");
			writerStream.println("\t}");
			
			writerStream.println("}");
			writerStream.println("");
			
			//break;
			
			writerStream.close();
			fileOS.close();
		}		
	}
}
